/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Text.RegularExpressions;

namespace DaveSexton.DocProject.Sandcastle.TopicManagement
{
	internal sealed partial class TopicFiltersControl : UserControl
	{
		#region Public Properties
		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public TopicManager Manager
		{
			get
			{
				return manager;
			}
			set
			{
				UpdateManagerEventHandlers(true);

				manager = value;

				UpdateManagerEventHandlers(true);

				if (manager == null)
				{
					Enabled = false;

					topicFiltersList.ClearFilters();
				}
				else
				{
					Enabled = true;

					try
					{
						topicFiltersList.LoadFilters(manager.DynamicFilters);
					}
					catch (System.IO.DirectoryNotFoundException) { }
					catch (System.IO.FileNotFoundException) { }
				}
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public ITopicHierarchy Hierarchy
		{
			set
			{
				hierarchy = value;
			}
		}
		#endregion

		#region Private / Protected
		private TopicNode SelectedNode
		{
			get
			{
				return hierarchy.SelectedNode;
			}
		}

		private Topic SelectedTopic
		{
			get
			{
				return hierarchy.CurrentTopic;
			}
		}

		private const int filterTabIndex = 0;

		private TopicManager manager;
		private ITopicHierarchy hierarchy;
		private readonly Dictionary<Type, TabPage> editorTabPages = new Dictionary<Type, TabPage>(8);
		private readonly Dictionary<Type, List<TopicNode>> allMatches = new Dictionary<Type, List<TopicNode>>(8);
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="TopicFiltersControl" /> class.
		/// </summary>
		public TopicFiltersControl()
		{
			InitializeComponent();

			editorTabPages.Add(typeof(RegexTopicFilterControl), regularExpressionTabPage);
			regularExpressionTabPage.Tag = regexTopicFilterControl;
			regexTopicFilterActionPanel.Tag = regexTopicFilterControl;

			editorTabPages.Add(typeof(CategoryTopicFilterControl), categoriesTabPage);
			categoriesTabPage.Tag = categoryTopicFilterControl;
			categoryTopicFilterActionPanel.Tag = categoryTopicFilterControl;

			((Bitmap) filteredPicture.Image).MakeTransparent(Color.Fuchsia);
		}
		#endregion

		#region Methods
		private void UpdateManagerEventHandlers(bool bind)
		{
			if (manager != null)
			{
				if (bind)
				{
					manager.Saving += manager_Saving;
				}
				else
				{
					manager.Saving -= manager_Saving;
				}
			}
		}

		/// <remarks>
		/// When hosted by a tool window, controls' focus events are not raised when a control in the VS IDE or hosted in a different tool window is focused.
		/// Therefore, if the dynamic filters list <see cref="DataGridView"/> is in edit mode when the tool window is closed, the changes are lost.  Forcing
		/// the control to end its edit and commit changes to the underlying data before saving solves this problem.
		/// </remarks>
		private void EnsureChangesCommitted()
		{
			topicFiltersList.EndEdits();
		}

		private TabPage GetEditorTab(Type editorType)
		{
			if (editorTabPages.ContainsKey(editorType))
				return editorTabPages[editorType];
			else if (!typeof(Control).IsAssignableFrom(editorType) || !typeof(ITopicFilterEditor).IsAssignableFrom(editorType))
				throw new InvalidOperationException(Resources.Errors.InvalidTopicFilterEditorType);

			ITopicFilterEditor editor = GetEditor(editorType);
			editor.CanApplyChanged += editor_CanApplyChanged;
			editor.CanSaveChanged += editor_CanSaveChanged;

			TabPage tab = new TabPage(editor.Title);
			tab.Tag = editor;

			Control control = (Control) editor;
			control.Dock = DockStyle.Fill;

			tab.Controls.Add(control);

			TopicFilterActionPanel actionPanel = new TopicFilterActionPanel();
			actionPanel.Tag = editor;
			actionPanel.Dock = DockStyle.Bottom;
			actionPanel.Save += actionPanel_Save;
			actionPanel.Apply += actionPanel_Apply;
			actionPanel.Find += actionPanel_Find;
			actionPanel.NextMatch += actionPanel_NextMatch;
			actionPanel.ApplyEnabled = editor.CanApply;
			actionPanel.SaveEnabled = editor.CanSave;

			tab.Controls.Add(actionPanel);

			filtersTabControl.TabPages.Insert(filtersTabControl.TabPages.IndexOf(savedFiltersTabPage), tab);

			editorTabPages.Add(editorType, tab);

			return tab;
		}

		private static ITopicFilterEditor GetEditor(Type editorType)
		{
			return (ITopicFilterEditor) Activator.CreateInstance(editorType);
		}

		public void LoadFilter(ITopicFilter filter)
		{
			Type editorType = filter.EditorType;

			if (editorType != null)
			{
				TabPage tab = GetEditorTab(editorType);
				ITopicFilterEditor editor = (ITopicFilterEditor) tab.Tag;

				ClearMatches(editorType);

				includeMatchingTopicsCheckBox.Checked = filter.Include;

				editor.LoadFilter(filter);

				filtersTabControl.SelectedTab = tab;
			}
		}

		public void SaveFilter(ITopicFilter filter)
		{
			if (filter == null)
				throw new ArgumentNullException("filter");

			filter.Include = includeMatchingTopicsCheckBox.Checked;

			manager.DynamicFilters.Add(filter);

			filtersTabControl.SelectedTab = savedFiltersTabPage;
			topicFiltersList.SelectedTopicFilter = filter;
		}

		private void NextMatch(Type editorType)
		{
			if (allMatches.ContainsKey(editorType))
			{
				IList<TopicNode> matches = allMatches[editorType];

				hierarchy.SelectNextTopic(delegate(TopicNode topic)
				{
					return matches.Contains(topic);
				});
			}
		}

		private static void EditFilter(ITopicFilter filter)
		{
			Type editorType = filter.EditorType;

			if (editorType != null)
			{
				ITopicFilterEditor editor = GetEditor(editorType);

				using (TopicFilterEditorHostDialog dialog = new TopicFilterEditorHostDialog())
				{
					dialog.Editor = editor;
					dialog.Filter = filter;

					dialog.ShowDialog(DocProjectEnvironment.MainWindow);
				}
			}
		}

		private void ApplyFilter(ITopicFilter filter, bool withoutLoad, bool findOnly)
		{
			if (filter == null)
				throw new ArgumentNullException("filter");

			TopicNode selectedNode = SelectedNode;

			TopicNode nextOnlyFromTopic = (findNextMatchOnlyCheckBox.Checked)
				? (TopicNode) ((selectedNode == null) ? hierarchy.FirstNode : selectedNode)
				: null;

			bool check = filter.Include;
			bool show = showMatchingTopicsCheckBox.Checked || (findOnly && nextOnlyFromTopic != null);

			List<TopicNode> matches = null;
			TabPage tab = null;
			TopicFilterActionPanel actionPanel = null;

			hierarchy.BeginUpdate();

			try
			{
				matches = PrepareForSearch(filter, out tab, out actionPanel);

				if (nextOnlyFromTopic != null)
				{
					TopicNode node = SearchNext(filter, findOnly, check, show, nextOnlyFromTopic);

					if (node != null)
						matches.Add(node);
				}
				else
					matches.AddRange(Search(filter, findOnly, check, show));
			}
			catch (Exception ex)
			{
				if (withoutLoad || tab == null)
					throw;
				else
				{
					filtersTabControl.SelectedTab = tab;

					Control errorHostControl = actionPanel.ErrorHostControl;
					errors.SetError(errorHostControl, ex.Message);
				}
			}
			finally
			{
				hierarchy.EndUpdate();

				if (!withoutLoad && matches != null && tab != null)
				{
					filtersTabControl.SelectedTab = tab;

					actionPanel.SearchComplete(findOnly, check, nextOnlyFromTopic != null, matches.Count);
				}
			}
		}

		private List<TopicNode> PrepareForSearch(ITopicFilter filter, out TabPage tab, out TopicFilterActionPanel actionPanel)
		{
			tab = null;
			actionPanel = null;

			foreach (Type type in editorTabPages.Keys)
				ClearMatches(type);

			List<TopicNode> matches;
			Type editorType = filter.EditorType;

			if (editorType != null)
			{
				tab = GetEditorTab(editorType);
				
				actionPanel = (TopicFilterActionPanel) tab.Controls[1];

				Control errorHostControl = actionPanel.ErrorHostControl;

				errors.SetError(errorHostControl, null);

				if (allMatches.ContainsKey(editorType))
				{
					matches = allMatches[editorType];

					if (matches == null)
						allMatches[editorType] = matches = new List<TopicNode>(128);
				}
				else
					allMatches.Add(editorType, matches = new List<TopicNode>(128));
			}
			else
				matches = new List<TopicNode>(128);

			return matches;
		}

		private TopicNode SearchNext(ITopicFilter filter, bool findOnly, bool check, bool show, TopicNode findNextAfter)
		{
			TopicNode matchingTopic = hierarchy.FindTopic(findNextAfter, 
				delegate(TopicNode topic)
				{
					return filter.IsMatch(topic.Topic);
				});

			if (matchingTopic != null)
				SelectTopicMatch(true, findOnly, check, show, matchingTopic);

			return matchingTopic;
		}

		private IEnumerable<TopicNode> Search(ITopicFilter filter, bool findOnly, bool check, bool show)
		{
			foreach (TopicNode node in hierarchy.FindTopics(
				delegate(TopicNode topic)
				{
					return filter.IsMatch(topic.Topic);
				}))
			{
				SelectTopicMatch(false, findOnly, check, show, node);

				yield return node;
			}
		}

		private void ClearMatches(Type editorType)
		{
			if (allMatches.ContainsKey(editorType))
			{
				IList<TopicNode> matches = allMatches[editorType];

				if (matches != null && matches.Count > 0)
				{
					hierarchy.SuspendLayout();

					try
					{
						foreach (TopicNode topic in matches)
							UnselectTopicMatch(topic);
					}
					finally
					{
						matches.Clear();

						hierarchy.ResumeLayout(true);
					}
				}
			}

			if (editorTabPages.ContainsKey(editorType))
			{
				TabPage tab = editorTabPages[editorType];
				TopicFilterActionPanel panel = (TopicFilterActionPanel) tab.Controls[1];

				panel.ResetSearch();
			}
		}

		private void SelectTopicMatch(bool nextOnly, bool findOnly, bool check, bool show, TopicNode node)
		{
			if (findOnly && !nextOnly)
				node.BackColor = Color.LightBlue;
			else
			{
				if (!nextOnly)
					node.BackColor = Color.LightGreen;

				if (!findOnly)
					node.Checked = check;
			}

			if (nextOnly)
				hierarchy.SelectedNode = node;

			if (show)
				node.EnsureVisible();
		}

		private static void UnselectTopicMatch(TopicNode node)
		{
			node.BackColor = Color.Empty;
		}

		private void UpdateActionPanel(Type editorType, bool forSave)
		{
			if (editorType != null && editorTabPages.ContainsKey(editorType))
			{
				TabPage tab = editorTabPages[editorType];
				ITopicFilterEditor editor = (ITopicFilterEditor) tab.Tag;
				TopicFilterActionPanel actionPanel = (TopicFilterActionPanel) tab.Controls[1];

				if (forSave)
					actionPanel.SaveEnabled = editor.CanSave;
				else
					actionPanel.ApplyEnabled = editor.CanApply;
			}
		}

		/// <summary> 
		/// Clean up any resources being used.
		/// </summary>
		/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				UpdateManagerEventHandlers(false);

				if (components != null)
					components.Dispose();
			}
			base.Dispose(disposing);
		}
		#endregion

		#region Event Handlers
		private void manager_Saving(object sender, TopicManagerChangesEventArgs e)
		{
			// When the topic editor is hosted in a tool window, changing the focus 
			// to the IDE does not cause the editor to lose focus or validate.
			// To ensure that all changes are known before the manager attempts to save,
			// validation must occur though since action is taken via a validating event
			// handler.
			EnsureChangesCommitted();
		}

		private void filteredPicture_Click(object sender, EventArgs e)
		{
			hierarchy.SelectNextTopic(delegate(TopicNode topic)
			{
				return !topic.Checked && !topic.Topic.HasMethodOverloads;
			});
		}

		private void findNextMatchOnlyCheckBox_CheckedChanged(object sender, EventArgs e)
		{
			if (findNextMatchOnlyCheckBox.Checked)
			{
				showMatchingTopicsCheckBox.Checked = true;
				showMatchingTopicsCheckBox.Enabled = false;
			}
			else
				showMatchingTopicsCheckBox.Enabled = true;
		}

		private void editor_CanApplyChanged(object sender, TopicFilterEditorTypeEventArgs e)
		{
			UpdateActionPanel(e.EditorType, false);
		}

		private void editor_CanSaveChanged(object sender, TopicFilterEditorTypeEventArgs e)
		{
			UpdateActionPanel(e.EditorType, true);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void topicFiltersList_ApplyFilter(object sender, TopicFilterEventArgs e)
		{
			try
			{
				ApplyFilter(e.Filter, true, false);
			}
			catch (Exception ex)
			{
				Log.Exception(ex, Resources.Text.TopicFilterListError);
				DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.TopicFilterListError);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void topicFiltersList_LoadFilter(object sender, TopicFilterEventArgs e)
		{
			try
			{
				LoadFilter(e.Filter);
			}
			catch (Exception ex)
			{
				Log.Exception(ex, Resources.Text.TopicFilterListError);
				DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.TopicFilterListError);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void topicFiltersList_EditFilter(object sender, TopicFilterEventArgs e)
		{
			topicFiltersList.SuspendChangeTracking();

			try
			{
				EditFilter(e.Filter);
			}
			catch (Exception ex)
			{
				Log.Exception(ex, Resources.Text.TopicFilterListError);
				DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.TopicFilterListError);
			}
			finally
			{
				topicFiltersList.ResumeChangeTracking();
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void actionPanel_Save(object sender, EventArgs e)
		{
			try
			{
				TopicFilterActionPanel panel = (TopicFilterActionPanel) sender;
				ITopicFilterEditor editor = (ITopicFilterEditor) panel.Tag;

				SaveFilter(editor.Filter);
			}
			catch (Exception ex)
			{
				Log.Exception(ex, Resources.Text.TopicFilterActionPanelError);
				DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.TopicFilterActionPanelError);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void actionPanel_Apply(object sender, EventArgs e)
		{
			try
			{
				TopicFilterActionPanel panel = (TopicFilterActionPanel) sender;
				ITopicFilterEditor editor = (ITopicFilterEditor) panel.Tag;

				ITopicFilter filter = editor.Filter;
				filter.Include = includeMatchingTopicsCheckBox.Checked;

				ApplyFilter(filter, false, false);
			}
			catch (Exception ex)
			{
				Log.Exception(ex, Resources.Text.TopicFilterActionPanelError);
				DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.TopicFilterActionPanelError);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void actionPanel_Find(object sender, EventArgs e)
		{
			try
			{
				TopicFilterActionPanel panel = (TopicFilterActionPanel) sender;
				ITopicFilterEditor editor = (ITopicFilterEditor) panel.Tag;

				ITopicFilter filter = editor.Filter;
				filter.Include = includeMatchingTopicsCheckBox.Checked;

				ApplyFilter(filter, false, true);
			}
			catch (Exception ex)
			{
				Log.Exception(ex, Resources.Text.TopicFilterActionPanelError);
				DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.TopicFilterActionPanelError);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void actionPanel_NextMatch(object sender, EventArgs e)
		{
			try
			{
				TopicFilterActionPanel panel = (TopicFilterActionPanel) sender;
				ITopicFilterEditor editor = (ITopicFilterEditor) panel.Tag;
				Type editorType = editor.GetType();

				NextMatch(editorType);
			}
			catch (Exception ex)
			{
				Log.Exception(ex, Resources.Text.TopicFilterActionPanelError);
				DocProjectEnvironment.ShowErrorDialog(ex, Resources.Text.TopicFilterActionPanelError);
			}
		}
		#endregion
	}
}
